﻿using System;
using System.Data.SqlClient;
using System.Data;

namespace ATM
{
    class MoneyOperator
    {
        private SqlConnection dbCon;
        private SqlTransaction withdrawTran;

        private void GetTransactions(string cardNumber)
        {
            using (dbCon)
            {
                SqlCommand cmdSelectTransactions = new SqlCommand("Select * From AccountTransactions Where " +
                "CardNumber = @cardNumber", dbCon);
                IsCardNumberNull(cardNumber);

                cmdSelectTransactions.Parameters.Add(new SqlParameter("@cardNumber", cardNumber));
                SqlDataReader reader = cmdSelectTransactions.ExecuteReader();
                using (reader)
                {
                    while (reader.Read())
                    {
                        int id = (int)reader["Id"];
                        string cardNum = (string)reader["CardNumber"];
                        DateTime transDate = (DateTime)reader["TransactionDate"];
                        decimal amount = (decimal)reader["Amount"];

                        Console.WriteLine("{0} card Number: {1} date: {2} amount: {3}", id, cardNum, transDate, amount);
                    }
                }
            }
        }

        private void WithdrawSum(string cardNumber, string cardPin, decimal sumToWithdraw)
        {
            SqlCommand withdraw = new SqlCommand("Update CardInformation Set CardCash = CardCash - @sumtoWithdraw "+
            "where CardPin = @cardPin and CardNumber = @cardNumber ",dbCon);
            IsCardNumberNull(cardNumber);
            IsCardPinNull(cardPin);
            withdraw.Transaction = withdrawTran;
            withdraw.Parameters.Add("@sumtoWithdraw", sumToWithdraw);
            withdraw.Parameters.Add("@cardPin", cardPin);
            withdraw.Parameters.Add("@cardNumber", cardNumber);

            withdraw.ExecuteNonQuery();
        }

        private bool IsSumavailable(string cardNumber, string cardPin, decimal sumToWithdraw)
        {
            SqlCommand checkSum = new SqlCommand("select count(*) from CardInformation where "+
                "CardNumber = @cardNumber and CardPin = @cardPin and CardCash > @sumToWithdraw", dbCon);
            IsCardNumberNull(cardNumber);
            IsCardPinNull(cardPin);
            checkSum.Transaction = withdrawTran;
            checkSum.Parameters.Add(new SqlParameter("@cardNumber", cardNumber));
            checkSum.Parameters.Add(new SqlParameter("@cardPin", cardPin));
            checkSum.Parameters.Add(new SqlParameter("@sumToWithdraw", sumToWithdraw));

            int numberOfAcounts = (int)checkSum.ExecuteScalar();
            if (numberOfAcounts == 1)
            {
                return true;
            }
            else 
            {
                throw new ArgumentOutOfRangeException("You don`t have enough money in your account");
            }
        }

        private bool ValidateCard(string cardNumber, string cardPin)
        {
            SqlCommand checkCard = new SqlCommand("select count(*) from CardInformation where "+
                "CardNumber = @cardNumber and CardPin = @cardPin", dbCon);
            IsCardNumberNull(cardNumber);

            IsCardPinNull(cardPin);
            checkCard.Parameters.Add(new SqlParameter("@cardNumber", cardNumber));
            checkCard.Parameters.Add(new SqlParameter("@cardPin", cardPin));
            checkCard.Transaction = withdrawTran;
            int numberOfCards = (int)checkCard.ExecuteScalar();

            if (numberOfCards > 0)
            {
                return true;
            }
            else 
            {
                throw new ArgumentOutOfRangeException("Invalid card number or card pin");
            }
        }
  
        private void IsCardPinNull(string cardPin)
        {
            if (cardPin == null)
            {
                throw new ArgumentNullException("Card pin can`t be null");
            }
        }
  
        private void IsCardNumberNull(string cardNumber)
        {
            if (cardNumber == null) 
            {
                throw new ArgumentNullException("Card number can`t be null");
            }
        }

        private void DocumentTransAction(string cardNumber, decimal sumToWithDraw) 
        {
            SqlCommand cmdInsertTrans = new SqlCommand("Insert Into AccountTransactions "+
            "(CardNumber, TransactionDate, Amount) Values (@cardNumber, @date, @sum)",dbCon);

            IsCardNumberNull(cardNumber);
            cmdInsertTrans.Parameters.Add(new SqlParameter("@cardNumber", cardNumber));
            cmdInsertTrans.Parameters.Add(new SqlParameter("@date", DateTime.Now));
            cmdInsertTrans.Parameters.Add(new SqlParameter("@sum", sumToWithDraw));
            cmdInsertTrans.Transaction = withdrawTran;
            cmdInsertTrans.ExecuteNonQuery();
        }

        public void Withdraw(string cardNumber, string cardPin, decimal sumToWithdraw) 
        {
            withdrawTran = dbCon.BeginTransaction(IsolationLevel.RepeatableRead);
            SqlCommand cmd = dbCon.CreateCommand();
            cmd.Transaction = withdrawTran;

            using (dbCon) 
            {
                try
                {
                    ValidateCard(cardNumber, cardPin);
                    IsSumavailable(cardNumber, cardPin, sumToWithdraw);
                    WithdrawSum(cardNumber, cardPin, sumToWithdraw);
                    DocumentTransAction(cardNumber, sumToWithdraw);
                    withdrawTran.Commit();
                }
                catch (SqlException ex)
                {
                    Console.WriteLine("Exception: {0}", ex.Message);
                    withdrawTran.Rollback();
                    Console.WriteLine("Transaction cancelled.");

                }
                catch (Exception ex) 
                {
                    Console.WriteLine("Exception: {0}", ex.Message);
                    withdrawTran.Rollback();
                    Console.WriteLine("Transaction cancelled.");
                }
            }
        }
  
        public void ConnectToDb()
        {
            dbCon = new SqlConnection("Server=.\\SQLEXPRESS; " +
                                      "Database=ATM; Integrated Security=true");
            this.dbCon.Open();
        }

        public void GetAllTransactionsForGivenCard(string cardNumber, string cardPin) 
        {
            ValidateCard(cardNumber, cardPin);
            GetTransactions(cardNumber);
        }
    }
}
